/*
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
 *    FieldMetaInfo.java
 *    Copyright (C) 2008-2012 University of Waikato, Hamilton, New Zealand
 *
 */

package weka.core.pmml;

import java.io.Serializable;

import org.w3c.dom.Element;

import weka.core.Attribute;

/**
 * Abstract superclass for various types of field meta data.
 * 
 * @author Mark Hall (mhall{[at]}pentaho{[dot]}com
 * @version $Revision 1.0 $
 */
public abstract class FieldMetaInfo implements Serializable {

    /** ID added to avoid warning */
    private static final long serialVersionUID = -6116715567129830143L;

    /**
     * Inner class for Values
     */
    public static class Value implements Serializable {

        /**
         * For serialization
         */
        private static final long serialVersionUID = -3981030320273649739L;

        /** The value */
        protected String m_value;

        /**
         * The display value (might hold a human readable value - e.g. product name
         * instead of cryptic code).
         */
        protected String m_displayValue;

        /**
         * Enumerated type for the property. A value can be valid, invalid or indicate a
         * value that should be considered as "missing".
         */
        public enum Property {
            VALID("valid"), INVALID("invalid"), MISSING("missing");

            private final String m_stringVal;

            Property(String name) {
                m_stringVal = name;
            }

            @Override
            public String toString() {
                return m_stringVal;
            }
        }

        protected Property m_property = Property.VALID;

        /**
         * Construct a value.
         * 
         * @param value the Element containing the value
         * @throws Exception if there is a problem constucting the value
         */
        protected Value(Element value) throws Exception {
            m_value = value.getAttribute("value");
            String displayV = value.getAttribute("displayValue");
            if (displayV != null && displayV.length() > 0) {
                m_displayValue = displayV;
            }
            String property = value.getAttribute("property");
            if (property != null && property.length() > 0) {
                for (Property p : Property.values()) {
                    if (p.toString().equals(property)) {
                        m_property = p;
                        break;
                    }
                }
            }
        }

        @Override
        public String toString() {
            String retV = m_value;
            if (m_displayValue != null) {
                retV += "(" + m_displayValue + "): " + m_property.toString();
            }
            return retV;
        }

        public String getValue() {
            return m_value;
        }

        public String getDisplayValue() {
            return m_displayValue;
        }

        public Property getProperty() {
            return m_property;
        }
    }

    /**
     * Inner class for an Interval.
     */
    public static class Interval implements Serializable {

        /**
         * For serialization
         */
        private static final long serialVersionUID = -7339790632684638012L;

        /** The left boundary value */
        protected double m_leftMargin = Double.NEGATIVE_INFINITY;

        /** The right boundary value */
        protected double m_rightMargin = Double.POSITIVE_INFINITY;

        /**
         * Enumerated type for the closure.
         */
        public enum Closure {
            OPENCLOSED("openClosed", "(", "]"), OPENOPEN("openOpen", "(", ")"), CLOSEDOPEN("closedOpen", "[", ")"), CLOSEDCLOSED("closedClosed", "[", "]");

            private final String m_stringVal;
            private final String m_left;
            private final String m_right;

            Closure(String name, String left, String right) {
                m_stringVal = name;
                m_left = left;
                m_right = right;
            }

            @Override
            public String toString() {
                return m_stringVal;
            }

            public String toString(double leftMargin, double rightMargin) {
                return m_left + leftMargin + "-" + rightMargin + m_right;
            }
        }

        protected Closure m_closure = Closure.OPENOPEN;

        /**
         * Construct an interval.
         * 
         * @param interval the Element containing the interval
         * @throws Exception if there is a problem constructing the interval
         */
        protected Interval(Element interval) throws Exception {
            String leftM = interval.getAttribute("leftMargin");
            try {
                m_leftMargin = Double.parseDouble(leftM);
            } catch (IllegalArgumentException ex) {
                throw new Exception("[Interval] Can't parse left margin as a number");
            }

            String rightM = interval.getAttribute("rightMargin");
            try {
                m_rightMargin = Double.parseDouble(rightM);
            } catch (IllegalArgumentException ex) {
                throw new Exception("[Interval] Can't parse right margin as a number");
            }

            String closure = interval.getAttribute("closure");
            if (closure == null || closure.length() == 0) {
                throw new Exception("[Interval] No closure specified!");
            }
            for (Closure c : Closure.values()) {
                if (c.toString().equals(closure)) {
                    m_closure = c;
                    break;
                }
            }
        }

        /**
         * Returns true if this interval contains the supplied value.
         * 
         * @param value the value to check
         * @return true if the interval contains the supplied value
         */
        public boolean containsValue(double value) {
            boolean result = false;

            switch (m_closure) {
            case OPENCLOSED:
                if (value > m_leftMargin && value <= m_rightMargin) {
                    result = true;
                }
                break;
            case OPENOPEN:
                if (value > m_leftMargin && value < m_rightMargin) {
                    result = true;
                }
                break;
            case CLOSEDOPEN:
                if (value >= m_leftMargin && value < m_rightMargin) {
                    result = true;
                }
                break;
            case CLOSEDCLOSED:
                if (value >= m_leftMargin && value <= m_rightMargin) {
                    result = true;
                }
                break;
            default:
                result = false;
                break;
            }

            return result;
        }

        @Override
        public String toString() {
            return m_closure.toString(m_leftMargin, m_rightMargin);
        }
    }

    // -----------------------------

    /** the name of the field */
    protected String m_fieldName;

    /**
     * Enumerated type for the Optype
     */
    public enum Optype {
        NONE("none"), CONTINUOUS("continuous"), CATEGORICAL("categorical"), ORDINAL("ordinal");

        private final String m_stringVal;

        Optype(String name) {
            m_stringVal = name;
        }

        @Override
        public String toString() {
            return m_stringVal;
        }
    }

    /** The optype for the target */
    protected Optype m_optype = Optype.NONE;

    /**
     * Get the optype.
     * 
     * @return the optype
     */
    public Optype getOptype() {
        return m_optype;
    }

    /**
     * Get the name of this field.
     * 
     * @return the name of this field
     */
    public String getFieldName() {
        return m_fieldName;
    }

    /**
     * Construct a new FieldMetaInfo.
     * 
     * @param field the Element containing the field
     */
    public FieldMetaInfo(Element field) {
        m_fieldName = field.getAttribute("name");

        String opType = field.getAttribute("optype");
        if (opType != null && opType.length() > 0) {
            for (Optype o : Optype.values()) {
                if (o.toString().equals(opType)) {
                    m_optype = o;
                    break;
                }
            }
        }
    }

    /**
     * Return this field as an Attribute.
     * 
     * @return an Attribute for this field.
     */
    public abstract Attribute getFieldAsAttribute();
}
